#pragma once

#include "stdafx.h"
#include "MotionEstimationProperties.h"
#include "QuantizationProperties.h"
#include "ConfigProperties.h"
#include "Filters.h"
#include "ThreadRunInfo.h"
#include "AviFileReader.h"
#include "AsfFileWriter.h"
#include "OutputType.h"

using namespace System;
using namespace System::Diagnostics;
using namespace System::Drawing;

namespace ManagedVC1
{
    using namespace Exceptions;

    public ref class VC1Encoder
    {
    private:
        CVC1Encoder* m_pEncoder;
        AutoArrayPtr<BYTE> m_vc1Data;
        unsigned long m_vc1BufferSize;
        bool m_cancel;
        static Object^ lockObj;

        AviFileReader m_FileReader;
        AsfFileWriter m_FileWriter;

        MotionEstimationProperties^ m_MEProperties;
        QuantizationProperties^ m_QuantProperties;
        Filters^ m_Filters;
        ConfigProperties^ m_ConfigProperties;

        Stopwatch m_vc1EncodeTime, m_aviReadTime;

        EventHandler<System::ComponentModel::ProgressChangedEventArgs^>^ m_ProgressChanged;

    public:
        VC1Encoder()
            : m_FileReader()
        {
            m_MEProperties = gcnew MotionEstimationProperties;
            m_QuantProperties = gcnew QuantizationProperties;
            m_Filters = gcnew Filters;
            m_ConfigProperties = gcnew ConfigProperties;

            m_pEncoder = new CVC1Encoder();
            if (m_pEncoder == nullptr)
            {
                throw gcnew OutOfMemoryException();
            }

            lockObj = gcnew Object();
        }

        ~VC1Encoder()
        {
            delete m_MEProperties;
            delete m_QuantProperties;
            delete m_Filters;
            delete m_ConfigProperties;

            delete m_pEncoder;
            m_pEncoder = nullptr;
        }

        event EventHandler<System::ComponentModel::ProgressChangedEventArgs^>^ ProgressChanged
		{
			void add(EventHandler<System::ComponentModel::ProgressChangedEventArgs^>^ p) { m_ProgressChanged += p; }
			void remove(EventHandler<System::ComponentModel::ProgressChangedEventArgs^>^ p) { m_ProgressChanged -= p; }
            void raise(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ args)
            {
                if (m_ProgressChanged != nullptr)
                {
                    m_ProgressChanged->BeginInvoke(sender, args, nullptr, nullptr); 
                }
            }
		}

        #pragma region Configuration_Properties

        property MotionEstimationProperties^ MotionEstimation
        {
            MotionEstimationProperties^ get() { return m_MEProperties; }
        }

        property QuantizationProperties^ Quantization
        {
            QuantizationProperties^ get() { return m_QuantProperties; }
        }

        property Filters^ Filter
        {
            Filters^ get() { return m_Filters; }
        }

        property ConfigProperties^ Config
        {
            ConfigProperties^ get() { return m_ConfigProperties; }
        }

        #pragma endregion

        #pragma region Advanced Properties

        property bool AdaptiveGOP
        {
            bool get()
            {
                BOOL bAdaptiveGOP;
                long hr = m_pEncoder->GetAdaptiveGOP(&bAdaptiveGOP);
                if (SUCCEEDED(hr)) return Convert::ToBoolean(bAdaptiveGOP);
                else throw ManagedVC1::Exceptions::ExceptionUtility::GetExceptionForHr(hr);
            }
            void set(bool value)
            {
                long hr = m_pEncoder->SetAdaptiveGOP(Convert::ToInt32(value));
                if (FAILED(hr)) throw ManagedVC1::Exceptions::ExceptionUtility::GetExceptionForHr(hr);
            }
        }

        /// <summary>The B-frame delta QP specifies the amount of the increase in QP for B-frames relative to the anchor frame 
        /// QP on a per-macroblock basis. The QP value is incremented in whole steps. A higher QP value means a higher compression ratio. 
        /// Increasing B-frame delta QP can sometimes result in better video quality because this can free up bits to better compress 
        /// the key frames from which the B-frames are temporally predicted.
        ///
        /// This method is only supported when Config.NumofBframes is greater than 0. Not supported for Simple profile.
        ///
        /// Recommendation: Only call this method if you must set a specific value. By default, the encoder will use dynamic B frame delta QP settings.
        /// </summary>
        property unsigned long BFrameDeltaQP
        {
            unsigned long get()
            {
                DWORD deltaQP;
                long hr = m_pEncoder->GetBFrameDeltaQP(&deltaQP);
                if (SUCCEEDED(hr)) return deltaQP;
                else throw ManagedVC1::Exceptions::ExceptionUtility::GetExceptionForHr(hr);
            }
            void set(unsigned long value)
            {
                long hr = m_pEncoder->SetBFrameDeltaQP(value);
                if (FAILED(hr)) throw ManagedVC1::Exceptions::ExceptionUtility::GetExceptionForHr(hr);
            }
        }

        property bool ClosedEntryPoint
        {
            bool get()
            {
                BOOL cep;
                long hr = m_pEncoder->GetClosedEntryPoint(&cep);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return Convert::ToBoolean(cep);
            }
            void set(bool value)
            {
                long hr = m_pEncoder->SetClosedEntryPoint(Convert::ToInt32(value));
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property Size^ DisplayResolution
        {
            Size^ get()
            {
                DWORD width, height;
                long hr = m_pEncoder->GetDisplayResolution(&height, &width);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return gcnew Size(width, height);
            }
            void set(Size^ value)
            {
                long hr = m_pEncoder->SetDisplayResolution(value->Height, value->Width);
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property Size^ EncodeResolution
        {
            Size^ get()
            {
                DWORD width, height;
                long hr = m_pEncoder->GetEncodeResolution(&height, &width);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return gcnew Size(width, height);
            }
            void set(Size^ value)
            {
                long hr = m_pEncoder->SetEncodeResolution(value->Height, value->Width);
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property Size^ PixelAspectRatio
        {
            Size^ get()
            {
                DWORD width, height;
                long hr = m_pEncoder->GetPixelAspectRatio(&height, &width);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return gcnew Size(width, height);
            }
            void set(Size^ value)
            {
                long hr = m_pEncoder->SetPixelAspectRatio(value->Height, value->Width);
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property unsigned long PixelAspectRatioIndex
        {
            unsigned long get()
            {
                DWORD kpp;
                long hr = m_pEncoder->GetPixelAspectRatioIndex(&kpp);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return kpp;
            }
            void set(unsigned long value)
            {
                long hr = m_pEncoder->SetPixelAspectRatioIndex(value);
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property unsigned long KeyPopReduction
        {
            unsigned long get()
            {
                DWORD kpp;
                long hr = m_pEncoder->GetKeyPopReduction(&kpp);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return kpp;
            }
            void set(unsigned long value)
            {
                long hr = m_pEncoder->SetKeyPopReduction(value);
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property bool LetterBoxPresent
        {
            bool get()
            {
                BOOL lbp;
                long hr = m_pEncoder->GetLetterBoxPresent(&lbp);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return Convert::ToBoolean(lbp);
            }
            void set(bool value)
            {
                long hr = m_pEncoder->SetLetterBoxPresent(Convert::ToInt32(value));
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property bool LookAhead
        {
            bool get()
            {
                BOOL lbp;
                long hr = m_pEncoder->GetLookAhead(&lbp);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return Convert::ToBoolean(lbp);
            }
            void set(bool value)
            {
                long hr = m_pEncoder->SetLookAhead(Convert::ToInt32(value));
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property OutputModeType OutputMode
        {
            OutputModeType get()
            {
                OutputType ot;
                long hr = m_pEncoder->GetOutputMode(&ot);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return (OutputModeType)ot;
            }
            void set(OutputModeType value)
            {
                long hr = m_pEncoder->SetOutputMode((OutputType)value);
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        property ThreadRunInfo^ NumThreads
        {
            ThreadRunInfo^ get()
            {
                unsigned long numThreads, affinityMask;
                long hr = m_pEncoder->GetNumOfThreads(&numThreads, &affinityMask);
                if (FAILED(hr)) throw ManagedVC1::Exceptions::ExceptionUtility::GetExceptionForHr(hr);

                return gcnew ThreadRunInfo(numThreads, affinityMask);
            }
            void set(ThreadRunInfo^ value)
            {
                long hr = m_pEncoder->SetNumOfThreads(value->NumThreads, value->AffinityMask);
                if (FAILED(hr)) throw ManagedVC1::Exceptions::ExceptionUtility::GetExceptionForHr(hr);
            }
        }

        property unsigned long MaxOutputBufferSize
        {
            unsigned long get()
            {
                unsigned long maxSize;
                long hr = m_pEncoder->GetMaxOutputBuffer(&maxSize);
                if (SUCCEEDED(hr)) return maxSize;
                else throw ManagedVC1::Exceptions::ExceptionUtility::GetExceptionForHr(hr);
            }
        }

        property unsigned long VideoType
        {
            unsigned long get()
            {
                unsigned long vtype;
                long hr = m_pEncoder->GetVideoType(&vtype);
                ExceptionUtility::ThrowExceptionForHr(hr);
                return vtype;
            }
            void set(unsigned long value)
            {
                long hr = m_pEncoder->SetVideoType(value);
                ExceptionUtility::ThrowExceptionForHr(hr);
            }
        }

        #pragma endregion

        void Initialize(String^ inputFile);
        void Encode(String^ outputFile, String^ profileName);
        void Cancel();

    private:
        void InitFileReader(String^ inputFile);
        void InitFileWriter(String^ outputFile, String^ profileName);
        void Analyze();
        void FlushVideo();
        void FlushAudio();
        void WriteAudioVideo(BYTE* pVC1Data, unsigned long vc1DataSize, __int64 tTimeStampVideo, FrameType pFt);
    };
}